<?php

/**
 * Description of x10
 *
 * @author prometheus
 */
class x10
{
	static protected $dedicatedObjects = array();

	/**
	 * A rendszer alapkonfigurációját tárolja.
	 * @var Array
	 */
	static public $config;
	
	/**
	 * A rendszer gyökérkönyvtárának teljes útvonala.
	 * @var String
	 */
	static public $systemRoot;

	/**
	 * Az alapértelmezett (x10) névtér teljes elérési útja.
	 * @var String
	 */
	static public $x10NSPath;

	/**
	 * A rendszer konfigurációs fájljait tároló mappa teljes elérési útja.
	 * @var String
	 */
	static public $configRoot;

	/**
	 * A böngészőben használt gyökérkönyvtár teljes elérési útja. Ha a rendszer
	 * közvetlenül a domain főkönyvtárából elérhető, akkor ennek értéke "/".
	 * @var String
	 */
	static public $browserRoot;

	/**
	 * A böngésző gyökérkönyvtárának szerver oldali teljes elérési útja.
	 * @var String
	 */
	static public $documentRoot;

	/**
	 * Rendszerinformációs tömb. Elemei:
	 * <ul>
	 * <li>version:<ul>
	 * <li>major: A rendszer fő verziószáma</li>
	 * <li>minor: A rendszer adott verziójának változatát jelölő verziószám</li>
	 * <li>label: Kiadástípust jelölő címke - például: dev, RC1, beta, stable.</li>
	 * </ul></li>
	 * <li>releaseName: A rendszer fantázia neve (egy adott verzió fejlesztői
	 * termékazonosítója)</li>
	 * </ul>
	 * @var Array
	 */
	static public $x10;

	/**
	 * Az alapértelmezett alkalmazás osztályának neve.
	 * @var String
	 */
	protected static $defaultAppClass = '';

	/**
	 * A felhasználó által a böngészőn keresztül a rendszerhez eljuttatott
	 * lekérés nyers formában.
	 * @var String
	 */
	static public $userQuery;

	/**
	 * A rendszer alapvető folyamatait indítja el.
	 * @param Array $config
	 */
	static public function init($config=NULL)
	{
		self::initConfig($config);
		self::initStates();
		self::initTIM();
	}

	/**
	 * Elindítja az alapértelmezett, vagy a felhasználó által kiválasztott
	 * alkalmazást.
	 */
	static public function run()
	{
		$appClass = self::$defaultAppClass;
		$app = new $appClass();
		$app->init();
		$app->run();
		$app->done();
	}

	/**
	 * Leállítja az alapvető rendszerfolyamatok működését.
	 */
	static public function done()
	{
		self::doneStates();
		self::doneTIM();
	}

	/**
	 * Bállítja a rendszer fő konfigurációjában egyes elérési utak pontos
	 * helyét, a fontosabb beállításokat elérhetővé teszi az osztályhoz
	 * tartozó tulajdonságokon keresztül.
	 * @param Array $config
	 */
	static protected function initConfig($config=array())
	{
		$sysRoot = realpath($config['paths']['folders']['systemRoot']).'/';
		$config['paths']['folders']['systemRoot'] = $sysRoot;
		$config['paths']['folders']['documentRoot'] = $sysRoot.$config['paths']['folders']['documentRoot'];
		$config['paths']['folders']['configRoot'] = $sysRoot.$config['paths']['folders']['configRoot'];
		$config['paths']['folders']['x10NSPath'] = $sysRoot.$config['paths']['folders']['x10NSPath'];
		$config['paths']['folders']['general'] = $sysRoot.$config['paths']['folders']['general'];
		if (DIRECTORY_SEPARATOR == '\\')
		{
			foreach ($config['paths']['folders'] as $k=>$v)
			{
				$config['paths']['folders'][$k] = str_replace('\\', '/', $v);
			}
		}

		@ob_start();
		@ob_clean();
		foreach ($config['defaults']['response'] as $k=>$v)
		{
			header($k.': '.$v);
		}
		
		self::$config = $config;
		self::$systemRoot = self::$config['paths']['folders']['systemRoot'];
		self::$x10NSPath = self::$config['paths']['folders']['x10NSPath'];
		self::$configRoot = self::$config['paths']['folders']['configRoot'];
		self::$browserRoot = self::$config['paths']['folders']['browserRoot'];
		self::$documentRoot = self::$config['paths']['folders']['documentRoot'];
		self::$x10 = self::$config['system'];
		self::$userQuery = substr($_SERVER['QUERY_STRING'], strlen(self::$browserRoot));
		self::$defaultAppClass = self::$config['defaults']['Application'];
	}

	/**
	 * Visszaadja az osztály nevéhez kapcsolódó konfigurációs fájl relatív
	 * útvonalát és nevét. A renlatív útvonal a megadott vagy aktuális domain-
	 * nek megfelelő mappát jelenti.
	 * @param String|Object $className Amennyiben objektum a paraméter, a
	 * metódus az objektum osztályát fogja figyelembe venni
	 * @param String $forDomain
	 * @return String
	 */
	static public function getConfigFileNameFor($className, $forDomain=NULL)
	{
		if (is_object($className))
		{
			$className = get_class($className);
		}
		if (is_null($forDomain))
		{
			$forDomain = $_SERVER['HTTP_HOST'];
		}
		$forDomain .= '/';
		$result = $forDomain.str_replace('_', '.', $className).'.config.json';
		return $result;
	}

	/**
	 * A névtér kezelők segítségével kikeresi egy adott osztály forrásának
	 * pontos helyét, és visszatér vele.
	 * @param String $className
	 * @return String
	 */
	static public function getPathForClass($className)
	{
		$ns = self::$dedicatedObjects['x10_NamespaceManager']->findByClassName($className);
		$result = dirname($ns->get()->locate($className)).'/';
		return $result;
	}

	/**
	 * Elindítja az állapotkezelőket, amennyiben még nincs mentett állapot,
	 * az init-kor használt osztályokat require-rel betölti egy statikus lista
	 * alapján, amit aztán átad a megfelelő kezelőnek.
	 */
	static protected function initStates()
	{
		$stateConfigPath = self::$configRoot.self::getConfigFileNameFor('x10_StateManager');
		if (file_exists($stateConfigPath) and is_file($stateConfigPath) and (is_readable($stateConfigPath)))
		{
			$stateConfig = json_decode(file_get_contents($stateConfigPath));
			$stateInitKey = NULL;
			foreach ($stateConfig->states as $k=>$cfg)
			{
				if ($cfg->name == 'init')
				{
					$stateInitKey = $k;
					break;
				}
			}
			if (!is_null($stateInitKey))
			{
				$stateConfig->states[$stateInitKey]->classesLoaded = TRUE;
				if (file_exists($stateConfig->states[$stateInitKey]->path.'packedSources.php'))
				{
					require(self::$systemRoot.$stateConfig->states[$stateInitKey]->path.'packedSources.php');
				}
				else
				{
					$requiredClassesList = array(
						'x10_GeneralClass',
						'x10_GeneralException',
						'x10_ConfigWrapper',
						'x10_Config',
						'x10_E_Manager',
						'x10_E_Manager_InvalidItemProvider',
						'x10_E_Manager_InvalidItemProviderType',
						'x10_E_Config',
						'x10_E_Config_FileNotAccessable',
						'x10_E_Config_ParseError',
						'x10_E_DuplicationOnDedicatedObject',
						'x10_E_NamespaceManager',
						'x10_E_NamespaceManager_ClassLoader',
						'x10_E_NamespaceManager_ClassLoader_SourceNotFound',
						'x10_E_NamespaceManager_ClassLoader_SourceUnaccessible',
						'x10_E_NamespaceManager_NamespaceNotExists',
						'x10_E_State',
						'x10_E_State_CannotCreateStoreFolder',
						'x10_E_Util',
						'x10_E_Util_JSON',
						'x10_E_Util_JSON_GeneralError',
						'x10_Util_URL',
						'x10_Util_JSON',
						'x10_Manager',
						'x10_Provider',
						'x10_Provider_Descriptor',
						'x10_NamespaceManager',
						'x10_NamespaceManager_ClassLoader',
						'x10_NamespaceManager_ClassLoaderProvider',
						'x10_StateManager',
						'x10_StateManager_State',
						'x10_StateManager_StateProvider',
						'x10_StateManager_ClassManager',
						'x10_StateManager_ClassProvider',
					);

					foreach ($requiredClassesList as $className)
					{
						require(self::locateClass($className));
					}

					$stateConfig->states[$stateInitKey]->classes = $requiredClassesList;
				}
			}
			else
			{
				trigger_error('Nem állapítható meg az "init" rendszerállapot tárolására szolgáló könyvtár helye!', E_USER_ERROR);
			}

			x10::dedicate('x10_NamespaceManager');
			$stMan = new x10_StateManager(new x10_Config(new x10_ConfigWrapper($stateConfig)));
			$stMan->setStateTo('init');
		}
		else
		{
			trigger_error('Nem tölthető be a rendszerállapotok konfigurációs állománya innen: <b>'.$stateConfigPath.'</b>', E_USER_ERROR);
		}
	}

	/**
	 * Elindítja a nyomkövetési információkat kezelő rendszert.
	 */
	static protected function initTIM()
	{
		new x10_TraceInfoManager();
	}

	/**
	 * Megállítja a nyomkövetési információkat kezelő rendszert.
	 */
	static protected function doneTIM()
	{
		self::$dedicatedObjects['x10_TraceInfoManager']->storeLogs();
	}

	/**
	 * Megállítja az állapotkezelőket.
	 */
	static protected function doneStates()
	{
		$stMan = self::dedicate('x10_StateManager');
		$stMan->setStateTo('done');
		foreach ($stMan as $provider)
		{
			$provider->get()->storeClasses();
		}
	}

	/**
	 * Beazonosítja egy osztály elérési útját, és visszatér vele.
	 * @param String $className
	 * @return String
	 */
	static protected function locateClass($className)
	{
		$classRelativePath = substr(str_replace('_', '/', $className), strpos($className, '_') + 1).'.php';
		$path = self::$x10NSPath.$classRelativePath;

		if (!file_exists($path))
		{
			trigger_error('Az osztály (<i>'.$className.'</i>) nem tölthető be a helyéről: <b>'.$path.'</b>', E_USER_ERROR);
		}
		else if (!is_file($path) or !is_readable($path))
		{
			trigger_error('Az osztály (<i>'.$className.'</i>) forrása nem hozzáférhető olvasásra itt: <b>'.$path.'</b>', E_USER_ERROR);
		}
		else
		{
			return $path;
		}
	}

	/**
	 * Egyedi példányt regisztáló/lekérdező metódus.
	 * <p>A kapott paraméter lehet osztálynév vagy objektum. Objektumot közvetlenül
	 * csak abban az esetben tanácsos dedikálni, ha dedikálás később nem
	 * történik meg ismét. Rendes használatban az osztálynév alapú dedikálás
	 * javallott, azzal elkerülhetőek az ütközések vagy szabványtalanságok.</p>
	 * <p>Egy osztályból csak egy példány dedikálandó. A dedikált példányok -
	 * osztályuk nevei alapján - egységesen érhetőek el így ezen metódus
	 * segítségével.</p>
	 * <p>Példányt dedikálttá tenni különösen akkor van értelme, ha az adott
	 * osztály metódusaira dinamikus módon (például változó segítségével)
	 * gyakran történik hivatkozás - a PHP-ben jeletősen gyorsabban kerül
	 * végrehajtásra egy ilyen hívás, mint a call_user_func() és hasonló
	 * eljárások alkalmazásával, főképp statikus metódusok esetén. Mindezek
	 * mellett a dedikálás fontos jelzése annak, hogy egy osztály a rendszer
	 * központi részévé válik. Többnyire a dedikálást kiválthatnák a statikus
	 * osztályok, viszont a késői statikus kötésből eredő akadályok, melyeket
	 * a PHP 5.3.0 verziójáig nem küszöböltek ki, ezek alkalmazása - a fenti
	 * sebesség-szempont mellett - ellenjavallott.</p>
	 * @param String|Object $object
	 * @return Object
	 * @throws x10_GeneralException
	 */
	static public function dedicate($object)
	{
		if (is_object($object))
		{
			$key = get_class($object);
		}
		else if (is_string($object))
		{
			$key = $object;
			if (!self::isDedicatedObjectExists($key)) $object = new $key();
		}
		else
		{
			throw new x10_GeneralException('A dedikáció meghiúsult: a dedicate() metódus nem megfelelő paramétert kapott!');
		}

		if (!self::isDedicatedObjectExists($key))
		{
			self::$dedicatedObjects[$key] = $object;
		}

		return self::$dedicatedObjects[$key];
	}

	/**
	 * Ellenőrzi, hogy a példányból létezik-e már dedikált, és amennyiben igen,
	 * x10_E_DuplicationOnDedicatedObject kivételt hoz létre.
	 * @param Object $object
	 * @throws x10_E_DuplicationOnDedicatedObject
	 */
	static public function ifDedicated($object)
	{
		$className = get_class($object);
		if (isset(self::$dedicatedObjects[$className]))
		{
			throw new x10_E_DuplicationOnDedicatedObject($className);
		}
	}
	
	/**
	 * @deprecated
	 */
	static public function dedicateObject($object)
	{
		$key = get_class($object);
		if (!isset(self::$dedicatedObjects[$key]))
		{
			self::$dedicatedObjects[$key] = $object;
		}
		return self::$dedicatedObjects[$key];
	}

	/**
	 * @deprecated
	 */
	static public function getDedicatedObject($className)
	{
		if (isset(self::$dedicatedObjects[$className]))
		{
			return self::$dedicatedObjects[$className];
		}
		else
		{
			return NULL;
		}
	}

	static public function isDedicatedObjectExists($className)
	{
		return isset(self::$dedicatedObjects[$className]);
	}
}

?>